﻿using System;
using System.Linq;
using System.Collections.Generic;
using MovieFactory.RayEngine.Samplers;

namespace MovieFactory.RayEngine {

    public class PathBuffer : IEnumerable<PathSampler> {
        public RayEngineScene Scene;
        public ISampler Sampler;

        public long statsRenderingStart;
        public long statsTotalSampleCount; 
        protected IPathSamplerAggregator film;
        protected SurfaceSampler surfaceSampler;

        protected int firstPath, lastPath, maxPaths;
        protected List<PathSampler> paths;

        public PathBuffer(int maxPaths, RayEngineScene scene, IPathSamplerAggregator agg,ISampler sm, SurfaceSampler ss)
        {
            this.maxPaths = maxPaths;
            this.surfaceSampler = ss;
            this.Sampler = sm;
            this.Scene = scene;
            this.film = agg;
            this.paths = new List<PathSampler>();
        }

        public virtual void SetupAggregator(IPathSamplerAggregator aggregator) { }

        internal virtual void ReInit()
        {
            for (var i = 0; i < paths.Count; ++i)
                paths[i].InitPath(this);
            firstPath = 0;
            film.Reset();

            statsRenderingStart = DateTime.UtcNow.Ticks / 1000;
            statsTotalSampleCount = 0;
        }

        internal void ClearPaths() {
            paths.Clear();
        }

        internal virtual void FillRayBuffer(RayBuffer rayBuffer)
        {
            if (paths.Count == 0) {
                // Need at least 2 paths
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());
                paths[0].InitPath(this);
                paths[1].InitPath(this);
                firstPath = 0;
            }

            bool allPathDone = true;
            lastPath = firstPath;
            for (; ; ) {
                if (!paths[lastPath].FillRayBuffer(rayBuffer)) {
                    // Not enough free space in the RayBuffer
                    allPathDone = false;
                    break;
                }

                lastPath = (lastPath + 1) % paths.Count;
                if (lastPath == firstPath)
                    break;
            }

            if (allPathDone) {
                var newPaths = 0;
                var maxNewPaths = maxPaths;

                for (; ; ) {
                    newPaths++;
                    if (newPaths > maxNewPaths) {
                        firstPath = 0;
                        lastPath = paths.Count - 1;
                        break;
                    }

                    var p = this.CreatePathSampler();
                    paths.Add(p);
                    p.InitPath(this);
                    if (!p.FillRayBuffer(rayBuffer)) {
                        firstPath = 0;
                        lastPath = paths.Count - 2;
                        break;
                    }
                }
            }
        }

        internal virtual void AdvancePaths(RayBuffer rayBuffer)
        {
            for (int i = firstPath; i != lastPath; i = (i + 1) % paths.Count) {
                paths[i].Advance(rayBuffer, film, item => item.InitPath(this));
            }
            firstPath = (lastPath + 1) % paths.Count;
        }

        protected virtual PathSampler CreatePathSampler()
        {
            return PathFactory.Instance.CreatePath(surfaceSampler);
        }

        #region IEnumerable<PathSampler> Members

        public IEnumerator<PathSampler> GetEnumerator() {
            return paths.Cast<PathSampler>().GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        #endregion
    }
}